
package client;



import generique.NombresPremiers;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SealedObject;

import com.sun.org.apache.xml.internal.security.utils.Base64;
import commun.Authenticite;
import commun.Chiffrement;
import commun.InterfaceRMIConnexionSecurisee;
import commun.PaquetAuthentification;
import commun.PaquetReponseRequete;
import commun.PaquetRequete;



/**
 * 
 * Noyau de l'application client.
 * 
 * @author Valentin Brémond
 * 
 * @version 1.0
 *
 */
public class KernelClient
{	
	/**
	 * Permet de calculer la clé via la méthode de Diffie-Hellman.
	 */
	private long x, X, G, n;
	
	/**
	 * La clé calculée par la méthode de Diffie-Hellman.
	 */
	private String cleDiffieHellman;

	/**
	 * Le login de l'utilisateur.
	 */
	private String login;
	
	/**
	 * Le hash du mot de passe.
	 */
	private String mdpHache;
	
	/**
	 * L'adresse du serveur.
	 */
	private String adresseServeur = "localhost";
	
	/**
	 * Stocke le serveur.
	 */
	private InterfaceRMIConnexionSecurisee serveurDistant;
	
	/**
	 * Stocke la fonction de hachage.
	 */
	private MessageDigest hachage;
	
	/**
	 * Stocke l'algorithme de chiffrement.
	 */
	private String algorithme = "AES";

	

	
	// CONSTRUCTEUR
	
	
	
	/**
	 * Constructeur par défaut.
	 */
	public KernelClient ()
	{
		super ();
		
		// On charge la fonction de hachage
		try
		{
			hachage = MessageDigest.getInstance ("SHA-512");
		}
		catch (NoSuchAlgorithmException e)
		{
			e.printStackTrace ();
		}
		
		// On essaie de se connecter au serveur
		try
		{
			serveurDistant = (InterfaceRMIConnexionSecurisee) Naming.lookup("rmi://" + adresseServeur + "/EtablissementConnexionSecurisee");
		}
		catch (MalformedURLException | RemoteException | NotBoundException e)
		{
			e.printStackTrace ();
		}
		
		// Si le retour n'est pas l'interface prévue, on arrête l'application
		if (!(serveurDistant instanceof InterfaceRMIConnexionSecurisee))
		{
			System.exit (0);
		}
		else
		{
			// Sinon on lance le programme
			try
			{
				lancementProgramme ();
			}
			catch (RemoteException e)
			{
				e.printStackTrace ();
			}
			catch (InvalidKeyException e)
			{
				e.printStackTrace ();
			}
			catch (IllegalBlockSizeException e)
			{
				e.printStackTrace ();
			}
			catch (IOException e)
			{
				e.printStackTrace ();
			}
			catch (ClassNotFoundException e)
			{
				e.printStackTrace ();
			}
			catch (BadPaddingException e)
			{
				e.printStackTrace ();
			}
			catch (NoSuchAlgorithmException e)
			{
				e.printStackTrace ();
			}
			catch (NoSuchPaddingException e)
			{
				e.printStackTrace ();
			}
		}
	}
	
	
	/**
	 * Main
	 */
	public static void main (String[] args) throws NoSuchAlgorithmException, NoSuchProviderException, UnsupportedEncodingException
	{
		new KernelClient ();
	}
	
	
	
	/**
	 * Le cœur de l'application.
	 * 
	 * @throws InvalidKeyException 
	 * @throws IOException 
	 * @throws IllegalBlockSizeException 
	 * @throws BadPaddingException 
	 * @throws ClassNotFoundException 
	 * @throws NoSuchPaddingException 
	 * @throws NoSuchAlgorithmException 
	 */
	private void lancementProgramme () throws InvalidKeyException, IllegalBlockSizeException, IOException, ClassNotFoundException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException
	{
		// On récupère le login et le mot de passe de l'utilisateur
		this.login = "Val";
		this.mdpHache = Base64.encode (hachage.digest ("azerty".getBytes ()));
		
		// On commence par se mettre d'accord sur une clé avec le serveur
		diffieHellman ();
		
		// Une fois que la clé est choisie, on envoie le paquet permettant de vérifier l'authenticité
		if (verifierAuthenticite ())
		{
			// Puis on fait une requête
			String reponseRequete = effectuerRequete ("Requête à effectuer.");
			
			// Enfin on envoie un message sur un canal non chiffré pour vérifier que Wireshark arrive bien à le lire, contrairement au reste des messages
			serveurDistant.testEnvoiMessageClair ("!!!!!   MESSAGE QUE WIRESHARK DEVRAIT VOIR   !!!!!");
		}
	}
	
	
	
	/**
	 * Permet de générer les valeurs nécessaires au calcul de la clé via la méthode de Diffie-Hellman.
	 * 
	 * @throws RemoteException 
	 */
	private void diffieHellman () throws RemoteException
	{
		// On génère un nombre G compris entre 1000 et 10000
		G = (long) (Math.random () * (10000 - 1000) + 1) + 1000;
		// On génère un nombre premier n compris entre ces deux valeurs
		n = NombresPremiers.genererNombrePremier (10000000, 1000000000);
		
		// On prépare les bornes pour générer un nombre aléatoire x
		long min = 2;
		long max = n - 1;
		
		// On génère le nombre aléatoire x
		this.x = (long) (Math.random () * ((max - min) + 1)) + min;
		// Puis on en déduit X
		this.X = (long) (Math.pow (G, x)) % n;
		
		// On envoie les nombres au serveur pour qu'il calcule la clé et on récupère son Y pour calculer nous aussi la clé
		long Y = serveurDistant.diffieHellman (G, n, X);
		
		// On calcule la clé
		long cleDH = (long) (Math.pow (Y, x)) % n;
		
		// On la rallonge pour qu'elle fasse 16 octets (16 chiffres)
		cleDiffieHellman = "" + cleDH + cleDH + cleDH;		
		cleDiffieHellman = cleDiffieHellman.substring (0, 16);
	}
	
	
	
	/**
	 * Permet de vérifier l'authenticité du serveur.
	 * 
	 * @return true si le serveur est authentique, false sinon.
	 * 
	 * @throws InvalidKeyException 
	 * @throws IOException 
	 * @throws IllegalBlockSizeException 
	 * @throws BadPaddingException 
	 * @throws ClassNotFoundException 
	 * @throws NoSuchPaddingException 
	 * @throws NoSuchAlgorithmException 
	 */
	private boolean verifierAuthenticite () throws InvalidKeyException, IllegalBlockSizeException, IOException, ClassNotFoundException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException
	{
		Chiffrement chiffrement = new Chiffrement (cleDiffieHellman, algorithme);
		
		SealedObject paquetAuthentification = serveurDistant.verifierAuthenticite (chiffrement.chiffrer (Authenticite.genererPaquetAuthentification (login, mdpHache, cleDiffieHellman)));
		
		if (paquetAuthentification == null)
		{	
			return false;
		}
		
		PaquetAuthentification paquetRecu = (PaquetAuthentification) chiffrement.dechiffrer (paquetAuthentification);
		
		if (Authenticite.verifierAuthenticite (cleDiffieHellman, mdpHache, paquetRecu))
		{	
			return true;
		}

		return false;
	}
	
	
	
	/**
	 * Permet d'effectuer une requête.
	 * 
	 * @param requete La requête à effectuer.
	 * 
	 * @return La réponse à cette requête, null si le client n'est pas authentifié.
	 * 
	 * @throws NoSuchPaddingException 
	 * @throws NoSuchAlgorithmException 
	 * @throws IOException 
	 * @throws BadPaddingException 
	 * @throws IllegalBlockSizeException 
	 * @throws ClassNotFoundException 
	 * @throws InvalidKeyException 
	 */
	private String effectuerRequete (String requete) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, ClassNotFoundException, IllegalBlockSizeException, BadPaddingException, IOException
	{
		Chiffrement chiffrement = new Chiffrement (cleDiffieHellman, algorithme);
		
		SealedObject reponseChiffree = serveurDistant.effectuerRequete (chiffrement.chiffrer (new PaquetRequete (requete)));
		
		if (reponseChiffree == null)
		{
			return null;
		}
		
		PaquetReponseRequete paquetReponseRequete = (PaquetReponseRequete) chiffrement.dechiffrer (reponseChiffree);
		
		return paquetReponseRequete.getReponse ();
	}
}
